﻿using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Runtime.CompilerServices;
using System.Threading;
using Abi.Newtonsoft.Json;
using UnityEditor;
using UnityEditor.PackageManager;
using UnityEditor.PackageManager.Requests;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UIElements;

namespace ABI.CCK.Interface
{
    
    public class ModuleWorkshop : EditorWindow
    {
        private static VisualTreeAsset tree;
        private static VisualElement row;

        private static List<ListObj> modules = new List<ListObj>();
        private static List<ListObj> installedModules = new List<ListObj>();
        
        private static ListRequest Request;
        private static List<string> InstalledPackages = new List<string>();

        private static ConcurrentDictionary<Guid, bool> SearchAction = new ConcurrentDictionary<Guid, bool>();
        
        private static bool loading = false;
        
        [MenuItem ("Alpha Blend Interactive/Module Workshop", false, 500)]
        public static void  ShowWindow () {
            EditorWindow w = EditorWindow.GetWindow(typeof(ModuleWorkshop), false, "CCK :: Module Workshop");

            w.maxSize = new Vector2(800, 600);
            w.minSize = w.maxSize;
            
            ((ModuleWorkshop) w).Configure(w);
        }

        public void Configure(EditorWindow w)
        {
            if (tree == null) tree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Assets/ABI.CCK/Interface/module-workshop.uxml");
            row = tree.CloneTree();
            w.rootVisualElement.Add(row);

            row.Q(className: "obj-list").Clear();
            
            row.Q<Button>("RefreshList").clicked += GetList;
            row.Q<ToolbarSearchField>("Search").RegisterValueChangedCallback(SearchValueChanged);
            ReadInstalledModules();
            GetList();
        }
    
        private void OnEnable()
        {
            foreach (EditorWindow w in Resources.FindObjectsOfTypeAll<EditorWindow>())
            {
                if (w.titleContent.text == "CCK :: Module Workshop")
                {
                    ((ModuleWorkshop) w).Configure(w);
                }
            }
            Request = Client.List();
            EditorApplication.update += WriteInstalledPackageManagerPackages;
        }

        private void WriteInstalledPackageManagerPackages()
        {
            if (Request.IsCompleted)
            {
                if (Request.Status == StatusCode.Success)
                    foreach (var package in Request.Result)
                        InstalledPackages.Add(package.name);
                else if (Request.Status >= StatusCode.Failure)
                    Debug.Log(Request.Error.message);

                EditorApplication.update -= WriteInstalledPackageManagerPackages;
            }
        }

        private void OnDestroy()
        {
            try
            {
                row.Q<Button>("RefreshList").clicked -= GetList;
                row.Q<ToolbarSearchField>("Search").UnregisterValueChangedCallback(SearchValueChanged);
            } catch {}
        }

        private static void SearchValueChanged(ChangeEvent<string> evt)
        {
            foreach (var key in SearchAction.Keys)
            {
                SearchAction.TryUpdate(key, false, SearchAction[key]);
            }
            FilterList(evt.newValue);
        }

        private static VisualElement GetListObject(string id, string name, string author, string description, string currentVersion, string fileSize)
        {
            VisualElement e = tree.CloneTree().Q(className: "gui-object");
            
            e.name = Guid.NewGuid().ToString();
            e.Q<Label>("ObjectName").text = name;
            e.Q<Label>("Author").text = $"By {author}";
            e.Q<Label>("Description").text = description;
            e.Q<Label>("Version").text = $"Current: {currentVersion}";
            e.Q<Label>("ObjectSize").text = fileSize;
            
            return e;
        }

        private static async void FilterList(string filter)
        {
            Guid g = Guid.NewGuid();
            SearchAction.TryAdd(g, true);
            if (string.IsNullOrEmpty(filter))
            {
                GetList();
                return;
            }
            List<ListObj> filteredObj = modules.FindAll(x => x.ObjectName.ToLower().Contains(filter.ToLower()) || x.ObjectDescription.ToLower().Contains(filter.ToLower()) || x.ObjectAuthor.ToLower().Contains(filter.ToLower()));
            row.Q(className: "obj-list").Clear();
            if (filteredObj.Count > 0)
            {
                foreach (var listObj in filteredObj)
                {
                    VisualElement e = GetListObject(listObj.ObjectId, listObj.ObjectName, listObj.ObjectAuthor, listObj.ObjectDescription, listObj.ObjectVersion, listObj.ObjectFileSize);
                    ListObj obj = installedModules.Find(x => x.ObjectId == listObj.ObjectId);
                    bool installed = obj != null;
                    e.name = listObj.ObjectId;

                    Button install = e.Q<Button>("Install");
                    Button uninstall = e.Q<Button>("Uninstall");
                    Button update = e.Q<Button>("Update");

                    using (UnityWebRequest uwr = UnityWebRequest.Get($"https://files.abidata.io/platform_workshop/cck/{listObj.ObjectId}.png"))
                    {
                        uwr.downloadHandler = new DownloadHandlerTexture();
                        await uwr.SendWebRequest();
                        e.Q("AssetImage").style.backgroundImage = new StyleBackground(DownloadHandlerTexture.GetContent(uwr));
                    }

                    if (installed)
                    {
                        install.SetEnabled(false);
                        install.SendToBack();
                        update.clicked += () => DownloadModule(listObj);
                        uninstall.clicked += () => UninstallModule(listObj);
                        if (obj.ObjectVersion == listObj.ObjectVersion)  update.SetEnabled(false);
                        e.Q<Label>("Version").text = $"Installed: {obj.ObjectVersion} | Current: {listObj.ObjectVersion}";
                    }
                    else
                    {
                        install.clicked += () => DownloadModule(listObj);
                        uninstall.SetEnabled(false);
                        update.SetEnabled(false);
                    }

                    SearchAction.TryGetValue(g, out bool allowedToAdd);
                    if (!allowedToAdd) return;
                    row.Q(className: "obj-list").Add(e);
                }
            }

            SearchAction.TryRemove(g, out bool s);
        }

        private static async void GetList()
        {
            if (loading) return;
            loading = true;
            
            row.Q(className: "obj-list").Clear();
            modules.Clear();
            using (HttpClient client = new HttpClient())
            {
                HttpResponseMessage res = await client.GetAsync("https://gateway.abi.network/v1/IWorkshop/GetAvailableUnityExtensions");
                var result = JsonConvert.DeserializeObject<List<ListObj>>(await res.Content.ReadAsStringAsync());
                if (result == null) return;
                foreach (var listObj in result)
                {
                    ListObj obj = installedModules.Find(x => x.ObjectId == listObj.ObjectId);
                    if (CheckIfInstalled(listObj.ObjectId) && obj == null)
                    {
                        installedModules.Add(listObj);
                        WriteInstalledModules();
                    }
                    VisualElement e = GetListObject(listObj.ObjectId, listObj.ObjectName, listObj.ObjectAuthor, listObj.ObjectDescription, listObj.ObjectVersion, listObj.ObjectFileSize);
                    bool installed = obj != null;
                    e.name = listObj.ObjectId;

                    Button install = e.Q<Button>("Install");
                    Button uninstall = e.Q<Button>("Uninstall");
                    Button update = e.Q<Button>("Update");

                    using (UnityWebRequest uwr = UnityWebRequest.Get($"https://files.abidata.io/platform_workshop/cck/{listObj.ObjectId}.png"))
                    {
                        uwr.downloadHandler = new DownloadHandlerTexture();
                        await uwr.SendWebRequest();
                        e.Q("AssetImage").style.backgroundImage = new StyleBackground(DownloadHandlerTexture.GetContent(uwr));
                    }

                    if (installed)
                    {
                        install.SetEnabled(false);
                        install.SendToBack();
                        update.clicked += () => DownloadModule(listObj);
                        uninstall.clicked += () => UninstallModule(listObj);
                        if (obj.ObjectVersion == listObj.ObjectVersion)  update.SetEnabled(false);
                        e.Q<Label>("Version").text = $"Installed: {obj.ObjectVersion} | Current: {listObj.ObjectVersion}";
                    }
                    else
                    {
                        install.clicked += () => DownloadModule(listObj, true);
                        uninstall.SetEnabled(false);
                        update.SetEnabled(false);
                    }

                    row.Q(className: "obj-list").Add(e);
                    modules.Add(listObj);
                }
            }

            loading = false;
        }
    
        private static async void DownloadModule(ListObj module, bool installed = false)
        {
            string msg1 = string.Empty;
            string msg2 = string.Empty;
            string msg3 = string.Empty;
            if (!CheckDependenciesGeneric(module.ObjectDependenciesTypes, out msg1) ||
                !CheckDependenciesModule(module.ObjectDependenciesModules, out msg2) ||
                !CheckDependenciesPackage(module.ObjectDependenciesPackages, out msg3))
            {
                string message = $"{CCKLocalizationProvider.GetLocalizedText("ABI_UI_MODULE_WORKSHOP_MISSING_DEPENDENCIES_WARNING_PREFACE")}:\n\n";

                if (!string.IsNullOrEmpty(msg1)) message += msg1;
                if (!string.IsNullOrEmpty(msg2)) message += msg2;
                if (!string.IsNullOrEmpty(msg3)) message += msg3;
                
                message += $"\n{CCKLocalizationProvider.GetLocalizedText("ABI_UI_MODULE_WORKSHOP_MISSING_DEPENDENCIES_FINAL_WARNING")}";
                EditorUtility.DisplayDialog(CCKLocalizationProvider.GetLocalizedText("ABI_UI_MODULE_WORKSHOP_MISSING_DEPENDENCIES_TITLE"),message, CCKLocalizationProvider.GetLocalizedText("ABI_UI_MODULE_WORKSHOP_MISSING_DEPENDENCIES_DIALOG_ACCEPT"));
                return;
            }
            using (HttpClient client = new HttpClient())
            {
                HttpResponseMessage res = await client.GetAsync($"https://files.abidata.io/platform_workshop/cck/{module.ObjectId}/{module.ObjectFileName}");
                string path = $"{Application.persistentDataPath}/{module.ObjectFileName}";
                var output = await res.Content.ReadAsByteArrayAsync();
                using (FileStream fs = new FileStream(path, FileMode.Create, FileAccess.ReadWrite))
                {
                    await fs.WriteAsync(output, 0, output.Length);
                }
                if (installed) installedModules.RemoveAll(x => x.ObjectId == module.ObjectId);
                if ((ModuleUpdateStrategy) module.ObjectUpdateStrategy == ModuleUpdateStrategy.DeleteAndReImport)
                {
                    if (Directory.Exists($"Assets/ABI.MODS/{module.ObjectId}")) Directory.Delete($"Assets/ABI.MODS/{module.ObjectId}", true);
                }
                installedModules.Add(module);
                WriteInstalledModules();
                AssetDatabase.ImportPackage(path, false);
            }
        }

        private static bool CheckDependenciesGeneric(string deps, out string message)
        {
            bool depsMet = true;
            message = string.Empty;
            foreach (string dep in deps.Split(','))
            {
                Type foundType = (from assembly in AppDomain.CurrentDomain.GetAssemblies()
                    from type in assembly.GetTypes()
                    where type.Name == dep
                    select type).FirstOrDefault();
                
                if (!string.IsNullOrEmpty(dep) && foundType == null)
                {
                    message += $"Generic Type: {dep}\n";
                    depsMet = false;
                }
            }
            return depsMet;
        }

        private static bool CheckDependenciesModule(string deps, out string message)
        {
            bool depsMet = true;
            message = string.Empty;
            foreach (string dep in deps.Split(','))
            {
                if (!string.IsNullOrEmpty(dep) && installedModules.Find(x => x.ObjectId == dep) == null)
                {
                    message += $"CCK Module: {modules.Find(x => x.ObjectId == dep)?.ObjectName}\n";
                    depsMet = false;
                }
            }
            return depsMet;
        }
        
        private static bool CheckDependenciesPackage(string deps, out string message)
        {
            bool depsMet = true;
            message = string.Empty;
            foreach (string dep in deps.Split(','))
            {
                if (!string.IsNullOrEmpty(dep) && InstalledPackages.Find(x => x == dep) == null)
                {
                    message += $"Package Manager: {dep}\n";
                    depsMet = false;
                }
            }
            return depsMet;
        }

        private static bool CheckIfInstalled(string objectId)
        {
            return Directory.Exists($"Assets/ABI.MODS/{objectId}");
        }
        
        private static void UninstallModule(ListObj module)
        {
            installedModules.Remove(module);
            if (!string.IsNullOrEmpty(module.ObjectUninstallRemoveSymbols))
            {
                foreach (string sym in module.ObjectUninstallRemoveSymbols.Split(','))
                {
                    string defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup);
                    if (defines.Contains(sym))
                    {
                        defines = defines.Replace(sym, "").Replace(";;", ";");
                        PlayerSettings.SetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup, defines);
                    }
                }
            }
            Directory.Delete($"Assets/ABI.MODS/{module.ObjectId}", true);
            WriteInstalledModules();
            AssetDatabase.Refresh();
        }

        private static void WriteInstalledModules()
        {
            File.WriteAllText($"{Application.persistentDataPath}/modules.json", JsonConvert.SerializeObject(installedModules));
        }

        private static void ReadInstalledModules()
        {
            if (!File.Exists($"{Application.persistentDataPath}/modules.json")) return;
            List<ListObj> inst = JsonConvert.DeserializeObject<List<ListObj>>(File.ReadAllText($"{Application.persistentDataPath}/modules.json"));
            installedModules.Clear();
            foreach (ListObj obj in inst)
            {
                if (CheckIfInstalled(obj.ObjectId)) installedModules.Add(obj);
            }
            WriteInstalledModules();
        }
    }

    [System.Serializable]
    class ListObj
    {
        public string ObjectId { get; set; }
        public string ObjectName { get; set; }
        public string ObjectDescription{ get; set; } 
        public string ObjectAuthor { get; set; }
        public string ObjectVersion { get; set; }
        public string ObjectFileName { get; set; }
        public string ObjectFileSize { get; set; }
        public int ObjectUpdateStrategy { get; set; }
        public string ObjectUninstallRemoveSymbols { get; set; }
        public string ObjectDependenciesTypes { get; set; }
        public string ObjectDependenciesModules { get; set; }
        public string ObjectDependenciesPackages { get; set; }
    }
    
    public class UnityWebRequestAwaiter : INotifyCompletion
    {
        private UnityWebRequestAsyncOperation asyncOp;
        private Action continuation;

        public UnityWebRequestAwaiter(UnityWebRequestAsyncOperation asyncOp)
        {
            this.asyncOp = asyncOp;
            asyncOp.completed += OnRequestCompleted;
        }

        public bool IsCompleted { get { return asyncOp.isDone; } }

        public void GetResult() { }

        public void OnCompleted(Action continuation)
        {
            this.continuation = continuation;
        }

        private void OnRequestCompleted(AsyncOperation obj)
        {
            continuation();
        }
    }

    public static class ExtensionMethods
    {
        public static UnityWebRequestAwaiter GetAwaiter(this UnityWebRequestAsyncOperation asyncOp)
        {
            return new UnityWebRequestAwaiter(asyncOp);
        }
    }

    public enum ModuleUpdateStrategy
    {
        OverImport = 0,
        DeleteAndReImport = 1
    }
    
}